/*
 * This file is part of the Refran Library
 * Copyright (C) 2009 César Enrique García
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

/** @file
 * $Author: cquike $
 * $Date: 2010-09-08 19:08:39 +0000 (Wed, 08 Sep 2010) $
 * $Revision: 143 $
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifndef REFRAN_RECIPE_FACTORY_H
#define REFRAN_RECIPE_FACTORY_H

#include <vector>
#include <mutex>
#include <map>
#include "Recipe.h"

namespace Refran
{
    /**
     * This class is a singleton that allows to instantiate different
     * Recipes based on a register of them.
     * TODO: Implement with Loki Singleton (http://loki-lib.sourceforge.net/)
     *       or ACE Singleton (). The problem
     * 		 with the current impl is that there is a resource leak, since
     *       the singleton is never deleted.
     *       UPDATE: This seems to be fixed. Valgrind detects none.
     *       The problem with ACE Singleton is that it uses the
     *       double-check idiom, which is not thread-safe.
     */
    class RecipeFactory
    {
        public:

        /**
         * Method to obtain the singleton instance and access the factory
         */
        static RecipeFactory& instance();

        /**
         * Destructor
         */
        virtual ~RecipeFactory();

        /**
         * Deletes the singleton instance
         */
        static void shutdown();

        /**
         * Insert a new creator that creates objects of type Recipe
         * @param recipeName The name of the recipe
         * @param version    The version of the recipe. Note that the default
         *                   of 0 allows other version with higher version
         *                   numbers to be selected before.
         */
        template<typename RecipeType>
        void insertCreator
            (const std::string& recipeName, unsigned int version = 0);

        /**
         * Remove a creator of a recipe. Note that all the versions will be
         * deleted. For a finer control of versions use
         * removeCreator(const std::string& recipeName, unsigned int version)
         */
        void removeCreator(const std::string& recipeName);

        /**
         * Remove a creator of a recipe at a given version number.
         */
        void removeCreator(const std::string& recipeName, unsigned int version);

        /**
         * Get a list of all the RecipeCreators that have been registered
         */
        std::vector<std::string> getRecipeNameList() const;

        /**
         * Get a list of all the versions available for a given recipe
         */
        std::vector<unsigned int> getRecipeVersions
            (const std::string& recipeName) const;

        /**
         * Returns a new Recipe instance from the specified type.
         * The memory deallocation is automatically handled by shared_ptr.
         * @param recipeName The identification name of the Recipe to create.
         *
         * NOTE: By default the highest available version of the recipe
         * will be created. If a finer control of versions is needed use
         * createRecipe(const std::string& recipeName, unsigned int version);
         */
        std::shared_ptr<Refran::Recipe> createRecipe
            (const std::string& recipeName) const;

        /**
         * Returns a new Recipe instance from the specified type and at a given
         * version.
         * The memory deallocation is automatically handled by shared_ptr.
         * @param recipeName The identification name of the Recipe to create.
         *
         * NOTE: By default the highest available version of the recipe
         * will be created. If a finer control of versions is needed use
         * createRecipe(const std::string& recipeName, unsigned int version);
         */
        std::shared_ptr<Refran::Recipe> createRecipe
            (const std::string& recipeName, unsigned int version) const;

        /**
         * Remove all the RecipeCreator objects.
         */
        void clear() throw ();

        private:

        /**
         *  Typedef for functions that create recipes
         */
        typedef std::shared_ptr<Refran::Recipe>  (*RecipeCreator)();

        /**
         * Default constructor. Made private to ensure a singleton
         */
        RecipeFactory() throw ();

        /**
         * Copy constructor. Made private to ensure a singleton
         */
        RecipeFactory(const RecipeFactory& rhs);

        /**
         * Copy operator. Made private to ensure a singleton
         */
        RecipeFactory& operator =(const RecipeFactory& rhs);

        /**
         * Retrieve all the versions <-> creators map for a recipe
         */
        std::map<unsigned int, RecipeCreator>
        getVersionsCreator_(const std::string& recipeName) const;

        /**
         * The container with the association between Recipe identification name
         * version and the concrete Recipe creator functions
         */
        std::map<std::string, std::map<unsigned int, RecipeCreator> >
            m_recipeNameVersionCreators;

        /**
         * The singleton instance
         */
        static std::unique_ptr<RecipeFactory>  m_instance;

        /**
         * The synchronization mutex
         */
        static std::mutex m_mutex;

    };

    /**
     * This function allows to allocate a new Recipe of type RecipeType
     */
    template<typename RecipeType>
        std::shared_ptr<Refran::Recipe> newRecipe();


}// namespace Refran

#include "RecipeFactory_T.cpp"

#endif // REFRAN_RECIPE_FACTORY_H
